from asynchttp import AsyncHTTPConnection
import collections

STATE_CONNECTING   = 1
STATE_CONNECTED    = 2
STATE_DISCONNECTED = 3

def call_if_not_none_and_callable(o, **kwargs):
    if o is not None and callable(o):
        o(**kwargs)

class http_evented(AsyncHTTPConnection, object):
    """
    This is an event based async HTTP client. It lets you register
    events which will be called whenever an HTTP request is completed.

    It sort of mimics the javascript way of making ajax calls, which
    I have started to like for various reasons -- the last of which
    is efficiency though ;)

    Use function parameter binding while using this module to get the
    maximum bang for your buck :D

    DO NOT use request pipelining with unordered responses along with
    this class if you are expecting your handlers to be called. You may
    however use standard HTTP request pipelining in which responses are
    guaranteed to be returned in the same order as the requests are made

    However, if you are handling re-ordering of responses at a higher
    layer, then you may use it as you feel free. Make sure that that you
    are prepared to handle the calling of the event handlers in ANY order
    when the response is received from the server. You can typically
    handle this by always registering the same function for every request
    that you make
    """
    def __init__(self, host_and_port, onConnected=None,
                 onClose=None, onException=None):
        self._connection_state = STATE_DISCONNECTED
        self._eventHandlers    = collections.deque()
        self._onClose          = onClose
        self._onException      = onException
        self._onConnected      = None
        self.reconnect(host_and_port, onConnected)

    def reconnect(self, host_and_port, onReconnect=None):
        """
        [Re]connect to the HTTP end point
        """
        if self._connection_state != STATE_CONNECTING and self._connection_state == STATE_DISCONNECTED:
            host, port = host_and_port
            self._connection_state = STATE_CONNECTING
            self._onConnected = onReconnect
            AsyncHTTPConnection.__init__(self, host, port)
            self.connect()

    def handle_response(self):
        """
        Called when a response from the server is received
        """
        call_if_not_none_and_callable(self._eventHandlers.popleft(),
                                      response=self.response)

    def handle_connect(self):
        """
        Called when the connection to the HTTP end point succeeds
        """
        print "http_evented::handle_connect"
        self._connection_state = STATE_CONNECTED
        super(http_evented, self).handle_connect()
        call_if_not_none_and_callable(self._onConnected)

    def handle_close(self):
        """
        Called when the connection is closed from the server end
        """
        self._connection_state = STATE_DISCONNECTED
        super(http_evented, self).handle_close()
        self._fail_all_pending_event_handlers()
        call_if_not_none_and_callable(self._onClose)

    def handle_error(self):
        super(http_evented, self).handle_error()
        self._perform_on_error_handling()

    def handle_expt(self):
        """
        Called in case an exception is thrown while executing code.
        This can also happen due to disconnection if the remote
        HTTP end point goes down
        """
        self._perform_on_error_handling()

    def _perform_on_error_handling(self):
        self._connection_state = STATE_DISCONNECTED
        self._fail_all_pending_event_handlers()
        call_if_not_none_and_callable(self._onException)

    def push_HTTP_request(self, method, url, body, headers, callback=None):
        """
        Just PUSH the request on to the request queue. It will be sent
        ONLY when pop_response() is called
        """
        self.request(method, url, body, headers)
        self.push_request()
        self._eventHandlers.append(callback)

    def make_HTTP_request(self, method, url, body, headers, callback=None):
        """
        Make an HTTP request to the other HTTP end point. The callable
        'callback' will be called with either no parameter OR a single
        parameter named 'response' which will hold the HTTP response
        object. If there is an error or the response could NOT be
        processed for some reason, then response=None is passed to the
        callback function
        """
        self.push_HTTP_request(method, url, body, headers, callback)
        self.pop_response()

    def _fail_all_pending_event_handlers(self):
        for eh in self._eventHandlers:
            call_if_not_none_and_callable(eh, response=None)
        self._eventHandlers.clear()
